import {
    cloneDeep,
    uniq,
    get,
    replace,
    isEmpty
} from 'lodash'
import router from '@/router'
import setting from '@/setting'

// 判定是否需要缓存
const isKeepAlive = (data: any) => get(data, 'meta.cache', false)

export default {
    namespaced: true,
    state: {
        // 可以在多页 tab 模式下显示的页面
        pool: [],
        // 当前显示的多页面列表
        opened: get(setting, 'page.opened', []),
        // 当前页面
        current: '',
        // 需要缓存的页面 name
        keepAlive: [],
        // 需要动态清除缓存的页面集合
        excludeAlive: []
    },
    actions: {
        /**
         * @class opened
         * @description 从持久化数据载入标签页列表
         *  这里需要手动调用才会生效，因为登录逻辑尚未调整，先不做
         * @param {Object} context
         */
        async openedLoad({
            state,
            commit,
            dispatch
        }: any) {
            return new Promise((resolve) => {
                // store 赋值
                dispatch('db/get', {
                    dbName: 'sys',
                    path: 'page.opened',
                    defaultValue: setting.page.opened,
                    user: true
                }, {
                    root: true
                }).then((value: any) => {
                    // 在处理函数中进行数据优化 过滤掉现在已经失效的页签或者已经改变了信息的页签
                    // 以 fullPath 字段为准
                    // 如果页面过多的话可能需要优化算法
                    // valid 有效列表 1, 1, 0, 1 => 有效, 有效, 失效, 有效
                    const valid: any = []
                    if (!isEmpty(value)) {
                        // 处理数据
                        state.opened = value
                            .map((opened: any) => {
                                // 忽略首页
                                if (opened.fullPath === '/index') {
                                    valid.push(1)
                                    return opened
                                }
                                // 尝试在所有的支持多标签页的页面里找到 name 匹配的页面
                                const find = state.pool.find((item: any) => item.name === opened.name)
                                // 记录有效或无效信息
                                valid.push(find ? 1 : 0)
                                // 返回合并后的数据 新的覆盖旧的
                                // 新的数据中一般不会携带 params 和 query, 所以旧的参数会留存
                                return Object.assign({}, opened, find)
                            })
                            .filter((opened: any, index: any) => valid[index] === 1)
                    }
                    // 根据 opened 数据生成缓存设置
                    commit('keepAliveRefresh');
                    resolve({});
                })
            });
        },
        /**
         * 将 opened 属性赋值并持久化 在这之前请先确保已经更新了 state.opened
         * @param {Object} context
         */
        async opened2db({
            state,
            dispatch
        }: any) {
            // 设置数据
            dispatch('db/set', {
                dbName: 'sys',
                path: 'page.opened',
                value: state.opened,
                user: true
            }, {
                root: true
            })
        },
        /**
         * @class opened
         * @description 更新页面列表上的某一项
         * @param {Object} context
         * @param {Object} payload { index, params, query, fullPath } 路由信息
         */
        async openedUpdate({
            state,
            dispatch
        }: any, {
            index,
            params,
            query,
            fullPath
        }: any) {
            // 更新页面列表某一项
            const page = state.opened[index]
            page.params = params || page.params
            page.query = query || page.query
            page.fullPath = fullPath || page.fullPath
            state.opened.splice(index, 1, page)
            // 持久化
            await dispatch('opened2db')
        },
        /**
         * @class opened
         * @description 重排页面列表上的某一项
         * @param {Object} context
         * @param {Object} payload { oldIndex, newIndex } 位置信息
         */
        async openedSort({
            state,
            dispatch
        }: any, {
            oldIndex,
            newIndex
        }: any) {
            // 重排页面列表某一项
            const page = state.opened[oldIndex]
            state.opened.splice(oldIndex, 1)
            state.opened.splice(newIndex, 0, page)
            // 持久化
            await dispatch('opened2db')
        },
        /**
         * @class opened
         * @description 新增一个 tag (打开一个页面)
         * @param {Object} context
         * @param {Object} payload new tag info
         */
        async add({
            state,
            commit,
            dispatch
        }: any, {
            tag,
            params,
            query,
            fullPath
        }: any) {
            // 设置新的 tag 在新打开一个以前没打开过的页面时使用
            const newTag = tag
            newTag.params = params || newTag.params
            newTag.query = query || newTag.query
            newTag.fullPath = fullPath || newTag.fullPath
            // 添加进当前显示的页面数组
            state.opened.push(newTag)
            // 如果这个页面需要缓存 将其添加到缓存设置
            if (isKeepAlive(newTag)) commit('keepAlivePush', tag.name)
            // 持久化
            await dispatch('opened2db')
        },
        /**
         * @class current
         * @description 打开一个新的页面
         * @param {Object} context
         * @param {Object} payload 从路由钩子的 to 对象上获取 { name, params, query, fullPath, meta } 路由信息
         */
        async open({
            state,
            commit,
            dispatch
        }: any, {
            name,
            params,
            query,
            fullPath,
            meta
        }: any) {
            // 已经打开的页面
            const opened = state.opened;
            // 判断此页面是否已经打开 并且记录位置
            let pageOpendIndex = 0
            const pageOpend = opened.find((page: any, index: any) => {
                const same = page.fullPath === fullPath
                pageOpendIndex = same ? index : pageOpendIndex
                return same
            })
            if (pageOpend) {
                // 页面以前打开过
                await dispatch('openedUpdate', {
                    index: pageOpendIndex,
                    params,
                    query,
                    fullPath
                })
            } else {
                // 页面以前没有打开过
                const page = state.pool.find((t: any) => t.name === name)
                // 如果这里没有找到 page 代表这个路由虽然在框架内 但是不参与标签页显示
                if (page) {
                    // 扩展功能，如果参数中有标题，重新设置标题
                    if (params.title) {
                        page.title = params.title;
                    }
                    await dispatch('add', {
                        tag: Object.assign({}, page),
                        params,
                        query,
                        fullPath
                    })
                }
            }
            // 如果这个页面需要缓存 将其添加到缓存设置
            if (isKeepAlive({
                meta
            })) commit('keepAlivePush', name)
            // 设置当前的页面
            commit('currentSet', fullPath)
        },
        /**
         * @class opened
         * @description 关闭一个 tag (关闭一个页面)
         * @param {Object} context
         * @param {Object} payload { tagName: 要关闭的标签名字 }
         */
        async close({
            state,
            dispatch
        }: any, { tagName, basePath, isUp = 0 }: any) {
            // 预定下个新页面
            let newPage = {}
            const isCurrent = state.current === tagName
            // 如果关闭的页面就是当前显示的页面
            if (isCurrent) {
                // 去找一个新的页面
                const len = state.opened.length
                for (let i = 0; i < len; i++) {
                    if (state.opened[i].fullPath === tagName) {
                        newPage = i < len - 1 ? state.opened[i + 1] : state.opened[i - 1]
                        break
                    }
                }
            }
            // 找到这个页面在已经打开的数据里是第几个
            const index = state.opened.findIndex((page: any) => page.fullPath === tagName)
            if (index >= 0) {
                // 如果这个页面是缓存的页面 将其在缓存设置中删除
                dispatch('keepAliveRemove', index)
            }
            // 持久化
            await dispatch('opened2db')
            if (basePath) {
                await router.push({
                    name: basePath,
                    params: {
                        isUp: isUp
                    }
                })
            } else if (isCurrent) {
                // 决定最后停留的页面
                const {
                    name = 'index', params = {}, query = {}
                }: any = newPage
                const routerObj = {
                    name,
                    params,
                    query
                }
                await router.push(routerObj)
            }
        },
        /**
         * @class opened
         * @description 关闭当前标签左边的标签
         * @param {Object} context
         * @param {Object} payload { pageSelect: 当前选中的tagName }
         */
        async closeLeft({
            state,
            dispatch
        }: any, {
            pageSelect
        }: any = {}) {
            const pageAim = pageSelect || state.current
            let currentIndex = 0
            state.opened.forEach((page: any, index: any) => {
                if (page.fullPath === pageAim) currentIndex = index
            })
            if (currentIndex > 0) {
                // 删除打开的页面 并在缓存设置中删除
                for (let i = state.opened.length - 1; i >= 0; i--) {
                    if (state.opened[i].name === 'index' || i >= currentIndex) {
                        continue
                    }
                    dispatch('keepAliveRemove', i)
                }
            }
            // 持久化
            await dispatch('opened2db')
            // 设置当前的页面
            state.current = pageAim
            if (router.app.$route.fullPath !== pageAim) await router.push(pageAim)
        },
        /**
         * @class opened
         * @description 关闭当前标签右边的标签
         * @param {Object} context
         * @param {Object} payload { pageSelect: 当前选中的tagName }
         */
        async closeRight({
            state,
            dispatch
        }: any, {
            pageSelect
        }: any = {}) {
            const pageAim = pageSelect || state.current
            let currentIndex = 0
            state.opened.forEach((page: any, index: any) => {
                if (page.fullPath === pageAim) currentIndex = index
            })
            // 删除打开的页面 并在缓存设置中删除
            for (let i = state.opened.length - 1; i >= 0; i--) {
                if (state.opened[i].name === 'index' || currentIndex >= i) {
                    continue
                }
                dispatch('keepAliveRemove', i)
            }
            // 持久化
            await dispatch('opened2db')
            // 设置当前的页面
            state.current = pageAim
            if (router.app.$route.fullPath !== pageAim) await router.push(pageAim)
        },
        /**
         * @class opened
         * @description 关闭当前激活之外的 tag
         * @param {Object} context
         * @param {Object} payload { pageSelect: 当前选中的tagName }
         */
        async closeOther({
            state,
            dispatch
        }: any, {
            pageSelect
        }: any = {}) {
            const pageAim = pageSelect || state.current
            let currentIndex = 0
            state.opened.forEach((page: any, index: any) => {
                if (page.fullPath === pageAim) currentIndex = index
            })
            // 删除打开的页面数据 并更新缓存设置
            for (let i = state.opened.length - 1; i >= 0; i--) {
                if (state.opened[i].name === 'index' || currentIndex === i) {
                    continue
                }
                dispatch('keepAliveRemove', i)
            }
            // 持久化
            await dispatch('opened2db')
            // 设置新的页面
            state.current = pageAim
            if (router.app.$route.fullPath !== pageAim) await router.push(pageAim)
        },
        /**
         * @class opened
         * @description 关闭所有 tag
         * @param {Object} context
         */
        async closeAll({
            state,
            dispatch
        }: any) {
            // 删除打开的页面 并在缓存设置中删除
            for (let i = state.opened.length - 1; i >= 0; i--) {
                if (state.opened[i].name === 'index') {
                    continue
                }
                dispatch('keepAliveRemove', i)
            }
            // 持久化
            await dispatch('opened2db')
            // 关闭所有的标签页后需要判断一次现在是不是在首页
            if (router.app.$route.name !== 'index') {
                await router.push({
                    name: 'index'
                })
            }
        },
        /**
         * 关闭一个页面并清除相关缓存
         * @class opened
         * @description 关闭所有 tag
         * @param {Object} context
         */
        async keepAliveRemove({
            state,
            commit,
            dispatch
        }: any, index: any) {
            const item = state.opened[index],
                id = item.params.id;
            // 根据页面id清除缓存
            // 如果没有id会根据name去清除
            dispatch('db/pageClearById', {
                name: item.name,
                id: id
            }, { root: true })
            commit('keepAliveRemove', index)
        },
        /**
        * 重载当前页面
        * @class opened
        * @description 关闭所有 tag
        * @param {Object} context
        */
        async reload({
            state
        }: any, { fullPath, name }: any) {
            fullPath = replace(fullPath, /\//g, "");
            if (fullPath != name) {
                // 判断fullPath和name是否一致，不一致说明是不同路由使用相同页面，需要记录到动态清除缓存的页面集合中
                // console.log('keepAliveRemove', fullPath);
                state.excludeAlive.push(fullPath);
            } else {
                const list = cloneDeep(state.keepAlive),
                    index = list.findIndex((item: any) => item === name);
                list.splice(index, 1)
                state.keepAlive = list;
            }
            router.replace({
                name: 'Refresh'
            })
        },
        /**
         * 重置需要动态销毁的页面
         *
         * @param {*} {
         *             state
         *         }
         */
        excludeAliveClean({
            state
        }: any) {
            state.excludeAlive = []
        }
    },
    mutations: {
        /**
         * @class keepAlive
         * @description 从已经打开的页面记录中更新需要缓存的页面记录
         * @param {Object} state state
         */
        keepAliveRefresh(state: any) {
            state.keepAlive = state.opened.filter((item: any) => isKeepAlive(item)).map((e: any) => e.name)
        },
        /**
         * @description 删除一个页面的缓存设置
         * @param {Object} state state
         * @param {String} name index 索引
         */
        keepAliveRemove(state: any, index: any) {
            const list = cloneDeep(state.keepAlive),
                item = state.opened[index],
                // 获取关闭页面的fullPath(去掉/)
                fullPath = replace(item.fullPath, /\//g, ""),
                name = item.name;
            // 判断fullPath和name是否一致，不一致说明是不同路由使用相同页面，需要记录到动态清除缓存的页面集合中
            if (fullPath !== name) {
                // console.log('keepAliveRemove', fullPath);
                state.excludeAlive.push(fullPath);
            }
            // 更新数据 关闭指定页面
            state.opened.splice(index, 1);
            // 因为keep-alive是根据页面name来缓存的，查找是否还有相同name的页面存在，如果存在则不删除缓存列表
            index = state.opened.findIndex((item: any) => item.name === name);
            if (index == -1) {
                // 查找缓存是否存在，删除缓存
                index = list.findIndex((item: any) => item === name);
                if (index !== -1) {
                    list.splice(index, 1)
                    state.keepAlive = list
                }
            }
        },
        /**
         * @description 增加一个页面的缓存设置
         * @param {Object} state state
         * @param {String} name name
         */
        keepAlivePush(state: any, name: any) {
            const keep = cloneDeep(state.keepAlive)
            keep.push(name)
            state.keepAlive = uniq(keep)
        },
        /**
         * @description 清空页面缓存设置
         * @param {Object} state state
         */
        keepAliveClean(state: any) {
            state.keepAlive = []
        },
        /**
         * @class current
         * @description 设置当前激活的页面 fullPath
         * @param {Object} state state
         * @param {String} fullPath new fullPath
         */
        currentSet(state: any, fullPath: any) {
            state.current = fullPath
        },
        /**
         * @class pool
         * @description 保存 pool (候选池)
         * @param {Object} state state
         * @param {Array} routes routes
         */
        init(state: any, routes: any) {
            const pool: any = []
            const push = function (routes: any) {
                routes.forEach((route: any) => {
                    if (route.children && route.children.length > 0) {
                        push(route.children)
                    } else {
                        if (!route.hidden) {
                            const {
                                meta,
                                name,
                                path
                            } = route
                            pool.push({
                                meta,
                                name,
                                path
                            })
                        }
                    }
                })
            }
            push(routes)
            state.pool = pool
        }
    },
    getters: {
        keepAlive(state: any) {
            return state.keepAlive;
        },
        excludeAlive(state: any) {
            return state.excludeAlive;
        }
    }
}